package io.cici.cc.mybatis.lite.session.defaults;

import io.cici.cc.mybatis.lite.exceptions.ExceptionFactory;
import io.cici.cc.mybatis.lite.executor.Executor;
import io.cici.cc.mybatis.lite.mapping.Environment;
import io.cici.cc.mybatis.lite.session.Configuration;
import io.cici.cc.mybatis.lite.session.ExecutorType;
import io.cici.cc.mybatis.lite.session.Session;
import io.cici.cc.mybatis.lite.session.SqlSessionFactory;
import io.cici.cc.mybatis.lite.transaction.Transaction;
import io.cici.cc.mybatis.lite.transaction.TransactionFactory;

import java.sql.Connection;
import java.sql.SQLException;

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public Session openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), false);
    }

    @Override
    public Session openSession(boolean autoCommit) {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), autoCommit);
    }

    @Override
    public Session openSession(ExecutorType execType) {
        return openSessionFromDataSource(execType, false);
    }

    @Override
    public Session openSession(ExecutorType execType, boolean autoCommit) {
        return openSessionFromDataSource(execType, autoCommit);
    }

    @Override
    public Session openSession(Connection connection) {
        return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
    }

    @Override
    public Session openSession(ExecutorType execType, Connection connection) {
        return openSessionFromConnection(execType, connection);
    }

    @Override
    public Configuration getConfiguration() {
        return configuration;
    }

    private Session openSessionFromDataSource(ExecutorType execType, boolean autoCommit) {
        Transaction tx = null;
        try {
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), autoCommit);
            final Executor executor = configuration.newExecutor(tx, execType);
            return new SessionImpl(configuration, executor, autoCommit);
        } catch (Exception e) {
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException(e);
        } finally {

        }
    }

    private Session openSessionFromConnection(ExecutorType execType, Connection connection) {
        try {
            boolean autoCommit;
            try {
                autoCommit = connection.getAutoCommit();
            } catch (SQLException e) {
                // Failover to true, as most poor drivers
                // or databases won't support transactions
                autoCommit = true;
            }
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            final Transaction tx = transactionFactory.newTransaction(connection);
            final Executor executor = configuration.newExecutor(tx, execType);
            return new SessionImpl(configuration, executor, autoCommit);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException(e);
        } finally {
        }
    }

    private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {

        return environment.getTransactionFactory();
    }

    private void closeTransaction(Transaction tx) {
        if (tx != null) {
            try {
                tx.close();
            } catch (SQLException ignore) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }

}
